// version - 1.1.0

// id - 终端标识 | 用来标记当前由谁发起 会携带在报文中
// path - 上报接口路径 | 跨域问题需自行解决，默认 POST JSON 方式上报
// logType - 日志类型 | ['click', 'error', 'request']
// requestingBefore - 调用请求接口前(为接口携带公共参数) | (params-接口参数, terminalInfo-终端信息) => 返回最终要使用的参数，如果未返回，则使用原有 params
// reportingBefore - 日志上报前(调整上报参数结构, 当前参数仅针对上报报文，缓存数据结构不会被调整) | (params-接口参数) => 返回最终要使用的参数，如果未返回，则使用原有 params

// 初始化 - log.init(obj/void 0)

//禁止直接访问实际实现方法，统一通过暴露方法调用

var log = (function () {

  var pathWarnNum = 0;//路径警告次数记录
  var log_version = '1.1.3';//版本记录

  //初始化
  function log_init(params) {

    if (JSON.stringify(infoStorage.config_info) !== "{}") {
      return console.error("【log.js】log has been initialized and cannot be reconfigured.");
    }

    if (params && Object.prototype.toString.call(params) === "[object Object]") {//配置模式启动
      params.pattern = "config";
      if (!params.logType) params.logType = ["click", "error", "request"];
      infoStorage.save("config_info", params);

      capture.start_all();
      request.init_all();
      infoQueue.loop();
      cache.init();

    } else {//默认模式启动
      var defaultInfo = {
        pattern: "default",
        logType: ["click", "error", "request"]
      }
      infoStorage.save("config_info", defaultInfo);

      capture.start_all();
      request.init_all();
      infoQueue.loop();
      cache.init();

    }
  }

  //时间记录器
  function duration_log(callBack, time) {
    var duration = 0;
    var t = null;
    if (!t) {
      var t = setInterval(function () {
        duration += time;
        callBack(infoStorage.terminal_info, duration);
      }, time * 1000);
      callBack(t, duration);
    }
  }

  //执行级错误捕捉
  function capture_execution_error() {
    window.onerror = function (message, source, lineno, colno, error) {
      var error_info = {
        timeStep: new Date().toString(),
        type: message,
        source: source,
        lineno: lineno,
        colno: colno,
        error: error,
        logType: "error",
        href: window.location.href,
      };
      infoStorage.save("error_info", error_info);
    };
  }

  //请求异常
  function capture_request_exception() {}

  //启动所有捕捉
  function capture_start_all(params) {
    for (var key in capture) {
      if (key === "start_all") {
        continue;
      }
      capture[key](params);
    }
  }

  //终端信息捕捉
  function capture_terminal_info() {

    getIP();

    var terminal_info = {
      resolving_power: screen.width + "*" + screen.height,
      operating_system: detectOS(),
      is_mobile_terminal: /(iPhone|iPad|iPod|iOS|Android)/i.test(navigator.userAgent),
      browser_core: judgeCore(),
      referrer: getReferrer()
    };
    infoStorage.save("terminal_info", terminal_info);

    function detectOS() {
      var sUserAgent = navigator.userAgent;
      var isWin = navigator.platform === "Win32" || navigator.platform === "Windows";
      var isMac = navigator.platform === "Mac68K" || navigator.platform === "MacPPC" || navigator.platform === "Macintosh" || navigator.platform === "MacIntel";
      if (isMac) return "Mac";
      var iPhone = sUserAgent.indexOf("iPhone") > -1 || sUserAgent.indexOf("iPhone") > -1;
      if (iPhone) return "iPhone";
      var isUnix = navigator.platform === "X11" && !isWin && !isMac;
      if (isUnix) return "Unix";
      var isLinux = String(navigator.platform).indexOf("Linux") > -1;
      var bIsAndroid = sUserAgent.toLowerCase().match(/android/i) === "android";
      if (isLinux) {
        if (bIsAndroid) return "Android";
        else return "Linux";
      }
      if (isWin) {
        var isWin2K =
          sUserAgent.indexOf("Windows NT 5.0") > -1 ||
          sUserAgent.indexOf("Windows 2000") > -1;
        if (isWin2K) return "Win2000";
        var isWinXP =
          sUserAgent.indexOf("Windows NT 5.1") > -1 ||
          sUserAgent.indexOf("Windows XP") > -1;
        if (isWinXP) return "WinXP";
        var isWin2003 =
          sUserAgent.indexOf("Windows NT 5.2") > -1 ||
          sUserAgent.indexOf("Windows 2003") > -1;
        if (isWin2003) return "Win2003";
        var isWinVista =
          sUserAgent.indexOf("Windows NT 6.0") > -1 ||
          sUserAgent.indexOf("Windows Vista") > -1;
        if (isWinVista) return "WinVista";
        var isWin7 =
          sUserAgent.indexOf("Windows NT 6.1") > -1 ||
          sUserAgent.indexOf("Windows 7") > -1;
        if (isWin7) return "Win7";
        var isWin8 =
          sUserAgent.indexOf("Windows NT 6.2") > -1 ||
          sUserAgent.indexOf("Windows 8") > -1;
        if (isWin8) return "Win8";
        var isWin81 =
          sUserAgent.indexOf("Windows NT 6.3") > -1 ||
          sUserAgent.indexOf("Windows 8.1") > -1;
        if (isWin81) return "Win8.1";
        var isWin10 =
          sUserAgent.indexOf("Windows NT 10.0") > -1 ||
          sUserAgent.indexOf("Windows 10") > -1;
        if (isWin10) return "Win10";
      }
      return "other";
    }

    function judgeCore() {
      var ua = navigator.userAgent;
      if (ua.indexOf("Trident") > -1) return "IE";
      if (ua.indexOf("Presto") > -1) return "Opera";
      if (ua.indexOf("AppleWebKit") > -1) return "WebKit";
      if (ua.indexOf("Gecko") > -1 && ua.indexOf("KHTML") === -1) return "Firfox";
      if (ua.indexOf("MicroMessenger") > -1) return "weixin";
      if (ua.match(/\sQQ/i) === "qq") return "qq";
      return "other";
    }

    function getReferrer(){
      var ref = '';
      if (document.referrer.length > 0) {
        ref = document.referrer;
      }else{
        ref = window.location.href;
      }
      return ref;
    }

    //获取ip等相关信息
    function getIP() {
      var ipElement = document.createElement("script");
      ipElement.src = location.protocol + "//pv.sohu.com/cityjson?ie=utf-8";

      ipElement.onload = function (e) {
        var obj = {
          ip: returnCitySN.cip,
          city: returnCitySN.cname,
        };
        infoStorage.save("terminal_info", obj);
        window.returnCitySN = null;
        document.head.removeChild(ipElement);
      };

      document.head.appendChild(ipElement);
    }
  }

  //点击事件捕捉
  function capture_loading_often() {
    window.onload = function () {
      capture_click_info(); //重新创建点击监听
    };
  }

  function capture_click_info() {
    var body = document.body;
    if (!body) return void 0;
    body.onclick = function (e) {
      var click_info = {
        logType: "click",
        elementType: e.target.localName,
        elementContent: e.target.innerText,
        timeStep: new Date().toString(),
        href: window.location.href,
      };
      infoStorage.save("click_info", click_info);
    };
  }

  function queue_push(info) {
    //是否携带 端 信息 - 默认携带
    info = Object.assign(info, infoStorage.terminal_info);

    //将终端标识塞入报文中
    infoStorage.config_info.id && (info.id = infoStorage.config_info.id);

    //信息进入队列后，将存储模块信息清理
    infoStorage.click_info = [];
    infoStorage.error_info = [];
    infoStorage.request_info = [];

    infoQueue.queue.push(info);
  }

  function queue_loop() {
    //消息发送器
    queue_loop.t = setInterval(function () {
      request.fetch();
    }, 5000);

    //消息收集器
    queue_loop.t1 = setInterval(function () {
      var errorSize = infoStorage.error_info.length;
      var clickSize = infoStorage.click_info.length;
      var requestSize = infoStorage.request_info.length;

      var param = {};

      if (find_Type("error") && errorSize) param.error_List = utils.deepCopy(infoStorage.error_info);
      if (find_Type("click") && clickSize) param.click_List = utils.deepCopy(infoStorage.click_info);
      if (find_Type("request") && requestSize) param.request_List = utils.deepCopy(infoStorage.request_info);

      ((find_Type("error") && errorSize) || (find_Type("click") && clickSize) || (find_Type("request") && requestSize)) && infoQueue.push(param);
    }, 4900);
  }

  function find_Type(type) {
    var logType = infoStorage.config_info.logType || [];
    return logType.join().indexOf(type) !== -1;
  }

  function utils_deep_copy(obj) {
    if (!obj) return {};
    var result = void 0;
    if (typeof obj === "object") {
      result = obj.constructor === Array ? [] : {};
      for (var i in obj) {
        typeof obj[i] === "object"
          ? (result[i] = utils_deep_copy(obj[i]))
          : obj.hasOwnProperty(i)
          ? (result[i] = obj[i])
          : void 0;
      }
    } else {
      result = obj;
    }
    return result;
  }

  function queue_shutdown() {
    clearInterval(queue_loop.t);
    clearInterval(queue_loop.t1);
    queue_loop.t = null;
    queue_loop.t1 = null;
  }

  function infoStorage_push(type, info) {
    if (type === "terminal_info" || type === "loading_info" || type === "config_info") {
      infoStorage[type] = Object.assign(infoStorage[type], info);
    } else {
      infoStorage[type].push(info);
    }
  }

  function request_cover_fetch() {
    var oldFetch = window.fetch;
    var requestingBefore = infoStorage.config_info.requestingBefore;
    var newFetch = function (url, options, isLogJs) {
      return new Promise((resolve, reject) => {
        var info = {
          url: url,
          timeStep: new Date().toString(),
          options: options,
          logType: "request",
          href: window.location.href,
        };
        !isLogJs && (infoQueue.isWait = false);
        !isLogJs && infoStorage.save("request_info", info);
        //如果配置 requestingBefore 且 接口存在参数
        if (requestingBefore && typeof requestingBefore === "function" && options.body) {
          options.body = requestingBefore(JSON.parse(options.body), infoStorage.terminal_info) ? JSON.stringify(requestingBefore(JSON.parse(options.body), infoStorage.terminal_info)) : options.body;
        }
        oldFetch(url, options)
          .then((res) => resolve(res))
          .catch((error) => {
            const errorInfo = {error, ...info, timeStep: new Date().toString()};
            !isLogJs && infoStorage.save("request_error_info", JSON.stringify(errorInfo));
            reject(error);
          });
      });
    };
    window.fetch = newFetch;
  }

  function request_cover_XMLHttpRequest() {
    const xhrOpen = XMLHttpRequest.prototype.open;
    const xhrSend = XMLHttpRequest.prototype.send;
    const xhrSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;

    const info = {
      url: "",
      options: {},
      logType: "request",
      href: window.location.href,
    };

    let isLogJs = false;

    XMLHttpRequest.prototype.setLogIn = function (_isLogJs) {
      isLogJs = _isLogJs;
    };

    XMLHttpRequest.prototype.open = function (method, url) {
      info.url = url;
      info.options = { method };
      xhrOpen.call(this, method, url);
    };

    XMLHttpRequest.prototype.send = function (body) {
      info.options = Object.assign(info.options, body);
      info.timeStep = new Date().toString();
      !isLogJs && (infoQueue.isWait = false);
      !isLogJs && infoStorage.save("request_info", info);
      xhrSend.call(this, body);

      this.onerror = (error) => {
        const errorInfo = {
          error,
          ...info,
          timeStep: new Date().toString(),
        };
        !isLogJs && infoStorage.save("request_error_info", JSON.stringify(errorInfo));
      };
    };

    XMLHttpRequest.prototype.setRequestHeader = function (header, value) {
      if (!info.options.headers) {
        info.options.headers = new Headers();
      }
      info.options.headers.append(header, value);
      xhrSetRequestHeader.call(this, header, value);
    };
  }

  //内部请求上报
  function request_fetch() {
    var param = infoQueue.queue.shift();
    var reportingBefore = infoStorage.config_info.reportingBefore;
    // if(!infoQueue.isWait){ return void 0; }
    if (!infoStorage.config_info.pattern === "default") {
      return void 0;
    }
    if (!infoStorage.config_info.path) {
      cache.save(param);
      if (!pathWarnNum) {
        pathWarnNum += 1;
        console.warn("The reporting address has not been configured, and the log will be cached locally.");
      }
      return void 0;
    }
    if (!param) {
      return void 0;
    }

    if (reportingBefore && typeof reportingBefore === "function") {
      param = reportingBefore(param) || param;
    }

    fetch(infoStorage.config_info.path, {
        method: "POST",
        body: JSON.stringify(param)
      },
      true
    ).then(function (res) {
      //todo 上报成功，如果消息被缓存，则清除缓存消息，否则，则什么都不做
    });
  }

  function request_init_all(params) {
    for (var key in request) {
      if (key === "init_all" || key === "fetch") {
        continue;
      }
      request[key](params);
    }
  }

  function cache_init() {
    initIndexDB();
    // if (storageAvailable("localStorage")) return (cache.type = "localStorage");
    // if (storageAvailable("sessionStorage")) return (cache.type = "sessionStorage");
    // return (cache.type = "Memory");

    function storageAvailable(type) {
      var storage;
      try {
        storage = window[type];
        var x = "__storage_test__";
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
      } catch (e) {
        return (
          e instanceof DOMException &&
          (e.code === 22 ||
            e.code === 1014 ||
            e.name === "QuotaExceededError" ||
            e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
          storage &&
          storage.length !== 0
        );
      }
    }

    //启动 indexDB
    function initIndexDB(){
      var indexDB = window.indexedDB.open('log.js');

      //错误监听
      indexDB.onerror = function (){
        console.error('【log.js】failed to open indexDB.');
        cache.indexDB = void 0;
      }

      //开启成功监听
      indexDB.onsuccess = function (event){
        cache.indexDB = indexDB.result;
        cache.type = 'indexDB';


        //增加数据插入方法
        cache.indexDB.addItem = function (data){
          var request = cache.indexDB.transaction(['log_table'], 'readwrite').objectStore('log_table').add(data);

          request.onsuccess = function (event){
            //数据入库成功
            cache.indexDB.readItem(1);
          }
          request.onerror = function (event){
            console.error('【log.js】failed to store data to indexDB.');
          }
        }


        //增加数据读取方法
        cache.indexDB.readItem = function (id){
          var transaction = cache.indexDB.transaction(['log_table']);
          var objectStore = transaction.objectStore('log_table');
          var request = objectStore.get(id);

          request.onsuccess = function (event){
            console.log(request.result);
          }
        }


      }

      //数据库升级事件
      indexDB.onupgradeneeded = function (event){
        cache.indexDB = event.target.result;
        if (!cache.indexDB.objectStoreNames.contains('log_table')) {
          cache.indexDB.store = cache.indexDB.createObjectStore('log_table', { keyPath: 'id', autoIncrement: true });
        }
      }

    }

  }

  function cache_save(item) {
    if (!item) return void 0;
    var type = cache.type;
    switch (type) {
      case 'indexDB':
        return cache.indexDB.addItem(item);
      case "localStorage":
        return localStorage.setItem("log_" + new Date().toString(), JSON.stringify(item));
      case "sessionStorage":
        return sessionStorage.setItem("log_" + new Date().toString(), JSON.stringify(item));
      default:
        infoStorage.expose_info["log_" + new Date().toString()] = JSON.stringify(item);
        break;
    }
  }

  function cache_del(key) {
    if (!key) return void 0;
    var type = cache.type;
    switch (type) {
      case "localStorage":
        return localStorage.removeItem(key);
      case "sessionStorage":
        return sessionStorage.removeItem(key);
      default:
        delete infoStorage.expose_info["log_" + new Date().toString()];
        break;
    }
  }

  function cache_clear() {
    var type = cache.type;
    switch (type) {
      case "localStorage":
        for (var key in localStorage) {
          if (key.indexOf("log_") !== -1) {
            localStorage.removeItem(key);
          }
        }
        return void 0;
      case "sessionStorage":
        for (var key1 in sessionStorage) {
          if (key1.indexOf("log_") !== -1) {
            sessionStorage.removeItem(key1);
          }
        }
        return void 0;
      default:
        infoStorage.expose_info = {};
        break;
    }
  }

  //请求模块
  var request = {
    cover_fetch: request_cover_fetch, //复写 fetch 实现
    cover_XMLHttpRequest: request_cover_XMLHttpRequest, //复写 xmlHttpRequest 实现
    fetch: request_fetch, //内部 fetch 请求
    init_all: request_init_all, //初始化所有请求模块
  };

  //捕获模块
  var capture = {
    execution_error: capture_execution_error, //执行错误
    request_exception: capture_request_exception, //请求异常
    loading_often: capture_loading_often, //加载时常
    terminal_info: capture_terminal_info, //终端信息获取
    click_info: capture_click_info, //点击信息获取
    start_all: capture_start_all, //启动所有监控
  };

  //缓存模块
  var cache = {
    type: "Memory", //使用缓存类型
    indexDB: void 0, //indexDB实例
    init: cache_init, //初始化缓存模块
    save: cache_save, //向缓存中存储
    del: cache_del, //清除缓存中某一项
    clear: cache_clear, //清除缓存中所有
  };

  //信息存储模块
  var infoStorage = {
    error_info: [], //错误信息存储
    loading_info: {}, //加载信息存储
    terminal_info: {}, //终端信息存储
    request_info: [], //请求信息存储
    config_info: {}, //配置模式信息存储
    click_info: [], //点击信息存储
    expose_info: {}, //对外暴露数据,
    request_error_info: [], // 请求的 error 存储
    save: infoStorage_push, //向信息存储模块存储消息
  };

  //消息推送队列
  var infoQueue = {
    queue: [], //待执行队列
    isWait: true, //是否可以进行上报(空闲模式标识，通过此识别是否在空闲模式，并上报信息)
    push: queue_push, //队列推入方法
    shutdown: queue_shutdown, //停止队列执行
    loop: queue_loop, //队列执行器
  };

  //工具模块
  var utils = {
    deepCopy: utils_deep_copy,
  };

  return {
    init: log_init,
    durationLog: duration_log,
    version: log_version
  };
})();

// export default log;
